#include <sys/socket.h>
#include <iostream>
#include <cerrno>
#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <cstdlib>

#include "network.h"

using namespace std;

//------------ Socket Exception---------
socketError::socketError(const char* estring)
{
	cerr<<estring<<" : "<<strerror(errno)<<endl;
}

//------------- TCP Client---------------

tcpSocket::tcpSocket()
{
	sockfd = Socket();
}

tcpSocket::tcpSocket(int fd)
{
	sockfd = fd;
}

int tcpSocket::Socket()
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
		throw socketError("tcpSocket::tcpSocket socket()");
	
	return sock;
}

int tcpSocket::Connect(const string &serAddr, unsigned short int port)
{
	struct sockaddr_in ssockaddr;
	bzero(&ssockaddr, sizeof(ssockaddr));
	ssockaddr.sin_family = AF_INET;
	ssockaddr.sin_port = htons(port);
	if(serAddr == "")
		ssockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		inet_pton(AF_INET, serAddr.c_str(), &ssockaddr.sin_addr);

	int res = TEMP_FAILURE_RETRY(connect(sockfd, (struct sockaddr*)&ssockaddr, sizeof(ssockaddr)));
	if(-1 == res)
		throw socketError("tcpSocket::Connect connect()");
	
	return res;
}

ssize_t tcpSocket::Recv(void *buff, size_t len)
{
	ssize_t rlen = TEMP_FAILURE_RETRY(recv(sockfd, buff, len, 0));

	if(-1 == rlen)
		throw socketError("tcpSocket::Recv recv()");
	
	return rlen;
}

ssize_t tcpSocket::Recvn(void *buff, size_t len)
{
	ssize_t left = len;

	while(left > 0)
		left -= Recv(buff, left);

	return len - left;
}

ssize_t tcpSocket::Send(const void *buff, size_t len)
{
	ssize_t slen = TEMP_FAILURE_RETRY(send(sockfd, buff, len, 0));
	
	if(-1 == slen)
		throw socketError("tcpSocket::Send send()");

	return slen;
}

tcpSocket::~tcpSocket()
{
	if(-1 == TEMP_FAILURE_RETRY(close(sockfd)))
		throw socketError("tcpSocket::~tcpSocket close()");
}

//---------------- TCP Server ----------
tcpServer::tcpServer()
{
	if(-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
		throw socketError("tcpServer::tcpServer socket()");
	
	int reuse = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}

bool tcpServer::Listen(unsigned short int port, const string &localAddr)
{
	struct sockaddr_in ssockaddr;
	bzero(&ssockaddr, sizeof(ssockaddr));
	ssockaddr.sin_family = AF_INET;
	ssockaddr.sin_port = htons(port);

	if(localAddr == "")
		ssockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		inet_pton(AF_INET, localAddr.c_str(), &ssockaddr.sin_addr);

	if(-1 == bind(sockfd, (sockaddr *)&ssockaddr, sizeof(ssockaddr)))
		throw socketError("tcpServer::Listen bind()");
	
	if(-1 == listen(sockfd, MAX_LISTEN))
		throw socketError("tcpServer::Listen listen()");
	
	return true;
}

tcpSocket tcpServer::waitForNewConnection()
{
	int sock = TEMP_FAILURE_RETRY(accept(sockfd, NULL, NULL));
	if(-1 == sock)
		throw socketError("tcpServer::waitForNewConnection accept()");
	
	return tcpSocket(sock);
}

tcpServer::~tcpServer()
{
	int res = TEMP_FAILURE_RETRY(close(sockfd));
	if(-1 == res)
		throw socketError("tcpServer::~tcpServer close()");
}

udpClient::udpClient()
{
	if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
		throw socketError("udpClient::udpClient socket()");
}

bool udpClient::Bind(const string &localAddr, unsigned short int port)
{
	struct sockaddr_in ssockaddr;
	bzero(&ssockaddr, sizeof(ssockaddr));
	ssockaddr.sin_family = AF_INET;
	ssockaddr.sin_port = htons(port);
	
	if(localAddr == "")
		ssockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		inet_pton(AF_INET, localAddr.c_str(), &ssockaddr.sin_addr);

	if(-1 == bind(sockfd, (sockaddr *)&ssockaddr, sizeof(ssockaddr)))
		throw socketError("udpClient::Bind bind()");
		
	return true;
}

ssize_t udpClient::Recvfrom(void *buff, size_t len, string &rAddr, unsigned short int &remotePort)
{
	struct sockaddr_in remoteAddr;
	bzero(&remoteAddr, sizeof(remoteAddr));
	socklen_t addrlen = sizeof(remoteAddr);

	int rlen = TEMP_FAILURE_RETRY(recvfrom(sockfd, buff, len, 0, (struct sockaddr*)&remoteAddr, &addrlen));
	if(-1 == rlen)
		throw socketError("udpClient::Recvfrom recvfrom()");
	
	char paddr[INET_ADDRSTRLEN];
	inet_ntop(AF_INET, &remoteAddr.sin_addr, paddr, INET_ADDRSTRLEN);
	rAddr = string(paddr);
	remotePort = ntohs(remoteAddr.sin_port);

	return rlen;
}

ssize_t udpClient::Sendto(const void *buff, size_t len,const string &addr, unsigned short int port)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	//cout<<"udpClient::Sendto "<<addr<<endl;
	struct sockaddr_in remoteAddr;
	bzero(&remoteAddr, sizeof(remoteAddr));
	remoteAddr.sin_family = AF_INET;
	remoteAddr.sin_port = htons(port);
	inet_pton(AF_INET, addr.c_str(), &remoteAddr.sin_addr);

	char paddr[INET_ADDRSTRLEN];
	cout<<"UDP send to "<<inet_ntop(AF_INET, &remoteAddr.sin_addr, paddr, INET_ADDRSTRLEN)<<":"<<ntohs(remoteAddr.sin_port)<<endl;

	//DBUG
	cout<<"--------------------"<<endl;
	for(size_t i=0;i<len;++i)
		printf("%02hhx ",*((char *)buff+i));
	cout<<endl;
	cout<<"+++++++++++++++++++"<<endl;
	//cout<<sockfd<<endl;
	int slen = TEMP_FAILURE_RETRY(sendto(sockfd, buff, len, 0, (struct sockaddr*)&remoteAddr, sizeof(struct sockaddr)));
	//cout<<errno<<endl;
	if(-1 == slen)
		throw socketError(" udpClient::Sendto sendto");

	close(sockfd);
	return slen;
}

void udpClient::broadcast(void *buff, size_t len, string interface, unsigned short int port)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	struct ifreq ifr;
	udpClient client;
	struct sockaddr_in broadcastAddr;
	
	bzero(&ifr, sizeof(ifr));
	strcpy(ifr.ifr_name, interface.c_str());

	if(-1 == ioctl(sockfd, SIOCGIFBRDADDR, &ifr))
	{
		perror("udpClient::broadcast ioctl()");
		exit(-1);
	}

	broadcastAddr.sin_family = AF_INET;
	broadcastAddr.sin_port = htons(port);
	broadcastAddr.sin_addr = ((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr;
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastAddr, sizeof(broadcastAddr));

	sendto(sockfd, buff, len, 0, (struct sockaddr*)&broadcastAddr, sizeof(broadcastAddr));
	char paddr[INET_ADDRSTRLEN];
	cout<<"udpClient::broadcast address "<<inet_ntop(AF_INET, &broadcastAddr.sin_addr, paddr, INET_ADDRSTRLEN)<<":"<<ntohs(broadcastAddr.sin_port)<<endl;

	//DBUG
	cout<<"++++++++++++++++++++"<<endl;
	for(size_t i=0;i<len;++i)
		printf("%02hhx ",*((char *)buff+i));
	cout<<endl;
	cout<<"--------------------"<<endl;
	close(sockfd);
}

udpClient::~udpClient()
{
	if(-1 == TEMP_FAILURE_RETRY(close(sockfd)))
		throw socketError("udpClient::~udpClient close()");
}
